package com.miyuan.wm.shiro;

import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import com.miyuan.wm.utils.NetworkUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

import com.miyuan.wm.data.UserInfo;
import com.miyuan.wm.pojo.PermissionEntity;
import com.miyuan.wm.pojo.RoleEntity;
import com.miyuan.wm.pojo.UserEntity;
import com.miyuan.wm.service.UserService;

import lombok.extern.slf4j.Slf4j;

/**
 * 把获取角色和权限信息的userService的两个接口提供给Shiro，让Shiro有法可依
 * <p>
 * 1. * 首先，UserRealm这个类继承了AuthorizingRealm，这个类的作用是两处获取信息，一处是Subject即用户传过来的信息；
 * 一处是我们提供给shiro的userService接口以获取权限信息和角色信息。拿这两个信息之后AuthorizingRealm会自动进行比较，判断用户名密码，用户权限等等。
 * </p>
 * <p>
 * 2.然后，拿用户凭证信息的是doGetAuthenticationInfo接口，拿角色权限信息的是doGetAuthorizationInfo接口
 * </p>
 * <p>
 * 3.
 * 然后，两个重要参数，AuthenticationToken是我们可以自己实现的用户凭证/密钥信息，PrincipalCollection是用户凭证信息集合。注意Principals表示凭证（比如用户名、手机号、邮箱等）
 * </p>
 * <p>
 * 4.最后，配置完成对比的两方之后Subject.login(token)的时候就会调用doGetAuthenticationInfo方法；
 * 涉及到Subject.hasRole或者Subject.hasPermission的时候就会调用doGetAuthorizationInfo方法；
 * </p>
 *
 *
 */
@Slf4j
public class UserRealm extends AuthorizingRealm {

	@Autowired
	private UserService userService;

	// shiro的权限信息配置
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
		try {
			String uname = (String) principals.getPrimaryPrincipal();
			List<RoleEntity> roles = userService.findRoles(uname);
			Set<String> roleNames = new HashSet<>(roles.size());
			if (CollectionUtils.isNotEmpty(roles)) {
				Iterator<RoleEntity> iterator = roles.iterator();
				while (iterator.hasNext()) {
					RoleEntity roleEntity = (RoleEntity) iterator.next();
					roleNames.add(roleEntity.getRoleName());
				}
			}
			// 此处把当前subject对应的所有角色信息交给shiro，调用hasRole的时候就根据这些role信息判断
			authorizationInfo.setRoles(roleNames);

			List<PermissionEntity> permissionEntities = userService.findPermissions(uname);
			Set<String> permissionNames = new HashSet<>(permissionEntities.size());
			if (CollectionUtils.isNotEmpty(permissionEntities)) {
				Iterator<PermissionEntity> iterator = permissionEntities.iterator();
				while (iterator.hasNext()) {
					PermissionEntity permissionEntity = (PermissionEntity) iterator.next();
					permissionNames.add(permissionEntity.getPerName());
				}
			}
			// 此处把当前subject对应的权限信息交给shiro，当调用hasPermission的时候就会根据这些信息判断
			authorizationInfo.setStringPermissions(permissionNames);
			return authorizationInfo;
		} catch (Exception e) {
			log.error("shiro的权限信息配置异常", e);
		}
		return authorizationInfo;
	}

	// 根据token获取认证信息authenticationInfo
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		SimpleAuthenticationInfo authenticationInfo = null;
		try {
			String uname = (String) token.getPrincipal();
			UserEntity user = userService.findUserByUserName(uname);
			if (user == null) {
				return null;
			}
			UserInfo userInfo = new UserInfo();
			userInfo.setUserID(user.getId());
			userInfo.setUserName(user.getUserName());
			userInfo.setPassword(user.getPassWord());
			userInfo.setAccessIP(NetworkUtil.currentIp());
			Subject currentSubject = SecurityUtils.getSubject();
			Session session = currentSubject.getSession();
			// 设置用户信息到 Session
			session.setAttribute("userInfo", userInfo);

			// 返回封装的认证信息
			authenticationInfo = new SimpleAuthenticationInfo(uname, user.getPassWord(), getName());
			// 清除 session 中的 userInfo 密码敏感信息
			userInfo.setPassword(null);

			return authenticationInfo;
		} catch (Exception e) {
			log.error("根据token获取认证信息authenticationInfo异常", e);
			throw new AuthenticationException();
		}
	}

}
